-- Simplified by not authenticating, and not dealing with
-- DSC, probably the single most complex command.
-- Apparently D-format commands don't work either. Whoops, oh well.
-- Doesn't validate SUP contents. Assumes +BASE is present.

adcHub = {}
adcHub.connections = {}		-- primarily for pre-CID logon.
adcHub.users = {}
adcHub.CID = ""

function createHubUser(socket)
	local _hu = {}
	_hu.state = "waiting_supports"
	_hu.info_parsed = {}
	_hu.socket = socket
	_hu.info = ""
	_hu.CID = ""
	_hu.command_buffer = ""		-- should be limited in size, to prevent abuse.

	_hu.OnData = function(this, data)
		-- ADC is UTF8, but the hub doesn't (mostly) have to care.
		data = string.gsub(data, "\\\n", "\2")		-- ugh. ADC's quoting needs to change.
		for w in string.gfind(data , "([^\n]*)\n") do
			this:ProcessMessage(string.gsub(w, "\2", "\\\n"))
		end
		local t1,t2,data = string.find(data , ".*\n(.*)")
		this.command_buffer = data
	end

	_hu.ProcessMessageLoggedIn = function(this, cmd, args)
		DC():PrintDebug("logged in")
		-- source CID's first parameter in A, B, and P messages, and the second in D.
		local bad = nil;
		local prefix = string.sub(cmd, 1, 1)

		-- coming in here, the command hasn't been stripped off
		-- (can't, see the 'B' handling in practab)
		local co = args()
		local fa = args()
		local sa = args()

		DC():PrintDebug("validating args")
		if prefix == "D" then
			if sa ~= this.CID then bad = true end
		elseif prefix == "A" or prefix == "B" or prefix == "P" then
			if fa ~= this.CID then bad = true end
		else
			bad = true
		end
		
		if bad then return end
		DC():PrintDebug("invalid args")

		local practab = {
			A = function() BroadcastActiveUDP(cmd .. "\n") end,
			B = function()
				if co == "BINF" then
					this:ParseInf(_parseIter(cmd))
					Broadcast(info .. "\n")
				else
					DC():PrintDebug("cmd: " .. cmd .. "\n")
					Broadcast(cmd .. "\n")
				end
			end,
			D = function()
				Send(cmd .. "\n")
				Send(fa, cmd .. "\n")
			end,
			P = function() BroadcastPassiveUDP(cmd .. "\n") end
		}

		local action = practab[prefix]
		if action ~= nil then action() end
	end

	_hu.ParseInf = function(this, args)
		-- first parameter contains CID; don't let users change after first INF
		if this.CID == "" then
			this.CID = args()
			if not OnNewCID(this.CID, this) then
				-- CID taken
				DC():PrintDebug("CID taken")
				return nil
			end
		end

		-- args() now feeds the inf values...
		while true do
			local infield = args()
			if infield == nil then break end

			if string.len(infield) >= 3 then
				local name = string.sub(infield, 1, 2)
				local value = string.sub(infield, 3)

				-- insert some I40.0.0.0 handling...
				-- but no way currently to grab remote IP address

				this.info_parsed[name] = value
			end
		end

		local infoNew = "BINF " .. this.CID
		for k,v in pairs(this.info_parsed) do infoNew = infoNew .. " " .. k .. v end
		this.info = infoNew

		return this.info_parsed.NI ~= nil
	end

	_hu.ProcessMessage = function(this, cmd)
		local args = _parseIter(cmd)

		local stt = {
			waiting_supports = function()
				-- don't really care what they sent.
				this:Send("ISUP +BASE\nIINF " .. adcHub.CID .. " HU1 HI1\n")
				this.state = "waiting_inf"
			end,
			waiting_inf = function()
				-- don't mostly care here either. yay. (Assume BINF...)
				-- however, can't afford to ignore errors entirely. bah.
				args()	-- throw away presumptive BINF
				if this:ParseInf(args) then
					this.state = "logged_in"
					Broadcast(this.info .. "\n")
					this:Send(GetInfoList(this.CID))
				end
			end,
			logged_in = function() this:ProcessMessageLoggedIn(cmd, args) end
		}

		stt[this.state]()
	end

	_hu.IsActiveUDP = function(this)
		return this.info_parsed.U4 ~= nil
	end

	_hu.IsLoggedIn = function(this)
		return this.state == "logged_in"
	end

	_hu.Send = function(this, data)
		DC():SocketWrite(this.socket, data)
	end

	return _hu
end

function OnHubInit()
	local base32table = {"A", "B", "C", "D", "E", "F", "G", "H",
	"I", "J", "K", "L", "M", "N", "O", "P",
		"Q", "R", "S", "T", "U", "V", "W", "X",
		"Y", "Z", "2", "3", "4", "5", "6", "7"}
	for i=1,12 do adcHub.CID = adcHub.CID .. base32table[math.random(1,32)] end
	adcHub.CID = adcHub.CID .. base32table[math.random(1,16)]
	RegisterSocketServer(600, adcHub)
end

function BroadcastPred(data, pred)
	for k,v in pairs(adcHub.users) do
		-- insert check for being logged in.
		if pred(v) then v:Send(data) end
	end
end

function BroadcastActiveUDP(data)
	BroadcastPred(data, function(user) return user:IsActiveUDP() end)
end

function BroadcastPassiveUDP(data)
	BroadcastPred(data, function(user) return not user:IsActiveUDP() end)
end

function Broadcast(data)
	BroadcastActiveUDP(data)
	BroadcastPassiveUDP(data)
end

function OnNewCID(CID, user)
	if adcHub.users[CID] == nil then
		adcHub.users[CID] = user
		return true
	end

	return nil		-- CID taken.
end

function Send(CID, data)
	local hu = adcHub.users[CID]
	if hubUser ~= nil then hu:Send(data) end
end

function GetInfoList(avoidCID)
	local inf = ""
	for k,v in adcHub.users do
		if v.CID ~= avoidCID then inf = inf .. v.info .. "\n" end
	end
	return inf
end

function adcHub.OnAccept(socket)
	adcHub.connections[socket] = createHubUser(socket)
end

function adcHub.OnRead(socket, data)
	local hu = adcHub.connections[socket]
	hu:OnData(data)
end

function adcHub.OnWrite(socket) end

function adcHub.OnClose(socket)
	local hu = adcHub.connections[socket]
	local CID = hu.CID
	adcHub.connections[socket] = nil
	if adcHub.users[hu.CID] == hu then adcHub.users[hu.CID] = nil end

	if hu:IsLoggedIn() then Broadcast("IQUI " .. CID .. " ND\n") end
end

OnHubInit()